/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package poproject.CombinatorialTask;

import poproject.Common.JaCoPEngine;
import java.util.ArrayList;
import JaCoP.constraints.Alldiff;
import JaCoP.constraints.Sum;
import JaCoP.constraints.SumWeight;
import JaCoP.constraints.XneqC;
import JaCoP.core.IntVar;
import JaCoP.core.Store;
import JaCoP.core.Var;
import JaCoP.search.DepthFirstSearch;
import JaCoP.search.IndomainMin;
import JaCoP.search.Search;
import JaCoP.search.SelectChoicePoint;
import JaCoP.search.SimpleSelect;
import JaCoP.search.SmallestDomain;
import java.util.HashSet;


/**
 * Klasa implementująca interfejs JaCoPEngine reprezentujący wspólne cechy
 * wszystkich problemów ograniczeniowych. Klasa umożliwia znalezienie rozwiązania
 * problemu kombinatorycznego na podstawie słów które użytkownik wprowadzi w GUI.
 * 
 * @author Jan Karaś
 */
public class CombinatorialTaskEngine implements JaCoPEngine{

    // ********************** POLA PRYWATNE ********************** //
    private ArrayList<Var> mVars;
    private Store mStore; 
    private Search mSearch;
    private ArrayList<Word> mWordList;      //< Pole reprezentujące listę słów proeblemu
    private HashSet<String> mUniqueLetters;  //< Pole przechowujące zbiów unikalnych liter problemu
    
    /** 
     * Konstruktor silnika do rozwiązywania zadania kombinatorycznego. 
     * 
     * @param wordList Lista słów, która będzie analizowana podczas rozwiązywanie problemu.
     */
    public CombinatorialTaskEngine(ArrayList<Word> wordList)
    {
        mStore = new Store();
        mVars = new ArrayList<>();
        mWordList = wordList;
        mUniqueLetters = new HashSet<>();
    }
    
    // ********************** METODY PUBLICZNE ********************** //   
    
    /**
     * Model problemu.
     * Metoda implementowana z interfejsu JaCoPEngine metodę pozwalającą
     * na stworzenie modelu problemu kombinatorycznego.
     */
    @Override
    public void createModel()
    {
        /**
         * Znalezienie unikalnych liter na podstawie słów wprowadzonych 
         * przez użytkownika.
         */
        findUniqueLetters();
        
        /**
         * Zdefiniowanie zmiennych.
         * Określenie dziedzin zmiennych i nadanie tym samym pierwszego
         * z ograniczeń na wartość jaką moga przyjmowac te zmienne.
         */
        ArrayList<IntVar> digits = new ArrayList<>();
        for(String str : mUniqueLetters)
            digits.add(new IntVar(mStore, str, 0, 9));   
         
        for(IntVar iv : digits)
            mVars.add(iv);
        
        /**
         * Różne wartości zmiennych.
         * Nałożenie na zmienne globalnego ograniczenia Alldiff zapewniającego,
         * że wszystkie zmienne występujące w problemie będą miały inną wartość.
         */
        mStore.impose(new Alldiff(digits));
                
        /**
         * Zmienne pomocnicze.
         * Tworzona jest lista słów zapisanych w formie listy zmiennych IntVar,
         * które z kolei odpowiadają poszczególnym literą słowa ale zapisanym
         * w sposób w pełni zrozumiały dla JaCoPa.
         * Zmienna ta jest wykorzystywana przy nakładaniu ograniczenia SumWeight.
         * Jej wprowadzenie zdecydowanie ułatwia nałożenie tego ograniczenia.
         */
        ArrayList<ArrayList<IntVar>> wordList2D = new ArrayList<>();
                
        for(Word w : mWordList)
        {
            String str = w.getWord();
            ArrayList<IntVar> word = new ArrayList<>(); 
            for(int i=0; i<str.length(); ++i)
            {
                String letter = Character.toString(str.charAt(i));
                for(IntVar iv : digits)
                {
                    if(letter.equals(iv.id))
                        word.add(iv);
                }
            }
            wordList2D.add(word);              
        }
        
        /**
         * Wartości słów (zmienne pomocnicze).
         * Stworzenie listy zmiennych pomocniczych,które reprezentują maksymalne
         * wartości jakie moga przyjąć poszczególne słowa. Lista ta jest 
         * wykorzystywana przez ograniczenie SumWeight, pozwalające ograniczyć 
         * wartość liter w słowie do zadaniej sumy warzonej.
         */
        ArrayList<IntVar> valueWords = new ArrayList<>();
        for(Word w : mWordList)
        {
            if(w.getWordSign() == Word.Sign.Minus)
                valueWords.add(new IntVar(mStore, "v("+w.getWord()+")",-w.getMaxWordValue(), 0));
            else
                valueWords.add(new IntVar(mStore, "v("+w.getWord()+")", 0, w.getMaxWordValue()));                
        }
        
        /**
         * Ograniczenie SumWeight.
         * Ograniczenie zapewniające odpowiednią wartość słowa.
         */
        for(int i=0; i<wordList2D.size(); ++i)
            mStore.impose(new SumWeight(wordList2D.get(i), mWordList.get(i).getWeights(), valueWords.get(i)));
       
        /**
         * Właściwe dodawanie.
         * 
         */
        /**
         * Lewa strona równania.
         * Wyciągnięcie z listy przedstawiającej wartości słów (valueWords)
         * tylko zmiennych znajdujących się po lewej stronie równania.
         */
        ArrayList<IntVar> variables = new ArrayList<>();
        for(int i=0; i<valueWords.size()-1; ++i)
            variables.add(valueWords.get(i));
        
        /**
         * Znalezienie prawej strony równania i zapisanie jej w zmiennej 
         * pomocniczej (sumOfVariables).
         */
        IntVar sumOfVariables = valueWords.get(valueWords.size()-1);
        
        /**
         * Suma słów.
         * Wykonanie działania dodawania na słowach wprowadzonych przez
         * użytkownika.
         */
        mStore.impose(new Sum(variables, sumOfVariables));
        
        /**
         * Ograniczenie na pierwsze litery słów.
         * Nałożenie ograniczenia na pierwsze litery wystepujące w słowach.
         * Pierwsze litery nie moga przyjąć wartości zerowych. Ogranicza to
         * nasz problem i jest to ograniczenie opcjonalne.
         */
        for(ArrayList<IntVar> listIV : wordList2D)
            mStore.impose(new XneqC(listIV.get(0),0));          
    }
    
    /**
     * Poszukiwanie rozwiązania.
     * Metoda implementowana z interfejsu JaCoPEngine metodę pozwalającą
     * na znalezienie rozwiązania problemu kombinatorycznego.
     * 
     * @return Wartość boolean informująca czy znaleziono rozwiązanie. 
     */
    @Override
    public boolean searchSolution()
    {
        SelectChoicePoint select = new SimpleSelect(mVars.toArray(new Var[1]), new SmallestDomain(), new IndomainMin());
        mSearch = new DepthFirstSearch();
        
        boolean result = mSearch.labeling(mStore, select);
        
        return result;   

    }
    
    /**
     * Pobranie meotdy poszukiwań.
     * Implementacja metody interfejsu pozwalająca na pobranie metody poszukiwań
     * problemu kombinatorycnzego w celu przetworzenia wyników poszukiwań do
     * reprezentacji w GUI.
     * 
     * @return Metoda poszukiwań
     */
    @Override
    public Search getSearchMethod()
    {
        return mSearch;
    }
    
    // ********************** METODY PRYWATNE ********************** //    
    
    /**
     * Litery unikalne.
     * Metoda prywatna umożlwiająca znaleznienie unikalnych liter występujących 
     * w problemie postawionym przez użytkownika.
     */
    private void findUniqueLetters()
    {
        for(Word w : mWordList)
        {
            String str = w.getWord();
            for(int i=0; i<str.length();i++)
                mUniqueLetters.add(Character.toString(str.charAt(i)));
        }   
    }   
}
